Motivation: Can I quantify the impact of the JP Morgan Healthcare Conference on healthcare stock value? Can I move beyond the truism that stocks change in response to the speeches, deals, and data presented at the meeting and describe the impact in detail? I’ve found some sources that attempt to quantify the impact of the meeting on stocks (see below).
Here is an example of a quantification of impact of JPM week on biotech stocks:
[https://www.cnbc.com/2017/01/04/betting-on-biotech-during-jpmorgans-big-health-care-conference-pays-off-history-shows.html]
Quotes:
## Parsed with column specification:
## cols(
## Symbol = col_character(),
## Name = col_character(),
## Industry = col_character()
## )
## Parsed with column specification:
## cols(
## price.open = col_double(),
## price.high = col_double(),
## price.low = col_double(),
## price.close = col_double(),
## volume = col_double(),
## price.adjusted = col_double(),
## ref.date = col_date(format = ""),
## ticker = col_character(),
## ret.adjusted.prices = col_double(),
## ret.closing.prices = col_double()
## )
S&P Index ticker is SPY and began in January 29, 1993.
## [1] "1993-01-29" "2019-05-24"
Also, it looks like I can download this as timeseries data (“ts”) getSymbols(‘F’,src=‘yahoo’,return.class=‘ts’)
NYSE Arca Biotechnology index (BTK), stated in January 2003.
## 'getSymbols' currently uses auto.assign=TRUE by default, but will
## use auto.assign=FALSE in 0.5-0. You will still be able to use
## 'loadSymbols' to automatically load data. getOption("getSymbols.env")
## and getOption("getSymbols.auto.assign") will still be checked for
## alternate defaults.
##
## This message is shown once per session and may be disabled by setting
## options("getSymbols.warning4.0"=FALSE). See ?getSymbols for details.
##
## WARNING: There have been significant changes to Yahoo Finance data.
## Please see the Warning section of '?getSymbols.yahoo' for details.
##
## This message is shown once per session and may be disabled by setting
## options("getSymbols.yahoo.warning"=FALSE).
## Warning: BTK contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "2003-01-03" "2019-06-03"
NASDAQ Biotechnology index: NBI, started in November 2003.
## [1] "2003-12-12" "2019-05-24"
I couldn’t find a website where is listed the JPM weeks over the history of the conference. Based on some searches of the previous names of the conference (Hambrecht & Quist Healthcare Conference, Chase H & Q, now JP Morgan) it seems like it’s always early January:
## [1] "1983-01-09" "1984-01-08" "1985-01-06" "1986-01-05" "1987-01-11"
## [6] "1988-01-10" "1989-01-08" "1990-01-07" "1991-01-06" "1992-01-05"
## [11] "1993-01-10" "1994-01-09" "1995-01-08" "1996-01-07" "1997-01-05"
## [16] "1998-01-11" "1999-01-10" "2000-01-09" "2001-01-07" "2002-01-06"
## [21] "2003-01-05" "2004-01-11" "2005-01-09" "2006-01-08" "2007-01-07"
## [26] "2008-01-06" "2009-01-11" "2010-01-10" "2011-01-09" "2012-01-08"
## [31] "2013-01-06" "2014-01-05" "2015-01-11" "2016-01-10" "2017-01-08"
## [36] "2018-01-07" "1983-01-10" "1984-01-09" "1985-01-07" "1986-01-06"
## [41] "1987-01-12" "1988-01-11" "1989-01-09" "1990-01-08" "1991-01-07"
## [46] "1992-01-06" "1993-01-11" "1994-01-10" "1995-01-09" "1996-01-08"
## [51] "1997-01-06" "1998-01-12" "1999-01-11" "2000-01-10" "2001-01-08"
## [56] "2002-01-07" "2003-01-06" "2004-01-12" "2005-01-10" "2006-01-09"
## [61] "2007-01-08" "2008-01-07" "2009-01-12" "2010-01-11" "2011-01-10"
## [66] "2012-01-09" "2013-01-07" "2014-01-06" "2015-01-12" "2016-01-11"
## [71] "2017-01-09" "2018-01-08" "1983-01-11" "1984-01-10" "1985-01-08"
## [76] "1986-01-07" "1987-01-13" "1988-01-12" "1989-01-10" "1990-01-09"
## [81] "1991-01-08" "1992-01-07" "1993-01-12" "1994-01-11" "1995-01-10"
## [86] "1996-01-09" "1997-01-07" "1998-01-13" "1999-01-12" "2000-01-11"
## [91] "2001-01-09" "2002-01-08" "2003-01-07" "2004-01-13" "2005-01-11"
## [96] "2006-01-10" "2007-01-09" "2008-01-08" "2009-01-13" "2010-01-12"
## [101] "2011-01-11" "2012-01-10" "2013-01-08" "2014-01-07" "2015-01-13"
## [106] "2016-01-12" "2017-01-10" "2018-01-09" "1983-01-12" "1984-01-11"
## [111] "1985-01-09" "1986-01-08" "1987-01-14" "1988-01-13" "1989-01-11"
## [116] "1990-01-10" "1991-01-09" "1992-01-08" "1993-01-13" "1994-01-12"
## [121] "1995-01-11" "1996-01-10" "1997-01-08" "1998-01-14" "1999-01-13"
## [126] "2000-01-12" "2001-01-10" "2002-01-09" "2003-01-08" "2004-01-14"
## [131] "2005-01-12" "2006-01-11" "2007-01-10" "2008-01-09" "2009-01-14"
## [136] "2010-01-13" "2011-01-12" "2012-01-11" "2013-01-09" "2014-01-08"
## [141] "2015-01-14" "2016-01-13" "2017-01-11" "2018-01-10" "1983-01-13"
## [146] "1984-01-12" "1985-01-10" "1986-01-09" "1987-01-15" "1988-01-14"
## [151] "1989-01-12" "1990-01-11" "1991-01-10" "1992-01-09" "1993-01-14"
## [156] "1994-01-13" "1995-01-12" "1996-01-11" "1997-01-09" "1998-01-15"
## [161] "1999-01-14" "2000-01-13" "2001-01-11" "2002-01-10" "2003-01-09"
## [166] "2004-01-15" "2005-01-13" "2006-01-12" "2007-01-11" "2008-01-10"
## [171] "2009-01-15" "2010-01-14" "2011-01-13" "2012-01-12" "2013-01-10"
## [176] "2014-01-09" "2015-01-15" "2016-01-14" "2017-01-12" "2018-01-11"
Cleaning the data, some thoughts:
## [1] 2014281 10
## [1] 1991750 10
Looks like we got rid of some bad data. Plot some price.high by company over the dates.
Looks good. These plots show that BatchGetSymbols() was able to handle companies as the entered and exited the stock market (unlike getSymbols()).
ggplot(SP500_index_daily_stocks, aes(x = ref.date, y = price.close)) +
geom_line() +
facet_wrap(~ticker, scales = 'free_y')
ggplot(biotech_index_daily_stocks, aes(x = ref.date, y = price.close)) +
geom_line() +
facet_wrap(~ticker, scales = 'free_y')
ggplot(BTK_index_daily_stocks, aes(x = ref.date, y = price.close)) +
geom_line() +
facet_wrap(~ticker, scales = 'free_y')
Given how spotty the data is prior to 2010, let’s just use NBI 2010 and later:
## [1] 3888 8
## [1] 2364 8
Dropped a bunch of useless rows. Woo. Plot again:
## quartz_off_screen
## 2
I’m using the word “fluctuations” here because I don’t have the technical expertise in finance to use a word like “volatility” which has a couple of specific definitions in finance. By “fluctuations” I mean that I’ll calculate the range in price for each stock each week of the year, then I’ll compare that range to the range over JPM week. This output will be a relative price range.
Calculate max net change (range) in price per day.## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 0.19 0.50 40.87 1.31 86100.00
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 0.00 0.00 8.86 15.79 54.30
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0 0 0 0 0 0
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0937 0.8800 1.2400 1.6385 2.3376 6.4800
Calculate max price and min price each JPM week (by year) and list as the price_range.Compare all other weeks to JPM week.
I’ll start with just 2017 and ticker == ABBV to simplify things, then build back up to all tickers and years. ABBV is “AbbVie is an American publicly traded biopharmaceutical company founded in 2013. It originated as a spin-off of Abbott Laboratories.” [https://en.wikipedia.org/wiki/AbbVie_Inc.]
## [1] 0.2598031 0.5833335 0.5024507 0.2377452 0.4730390 0.2303928 0.4705875
## [8] 0.8749986 0.3651954 0.2622548 0.2769599 0.3137248 0.6029406 0.4534316
## [15] 0.7941168 0.3578430 0.5612725 0.5661755 0.4338226 0.4166657 0.7230401
## [22] 0.4583338 0.6691163 0.3995089 0.3872532 0.4730390 0.8063724 1.3161766
## [29] 0.3774510 0.4411770 0.4460782 0.7034298 0.8431373 2.7843106 1.3014712
## [36] 0.6691163 1.9019586 0.5931365 0.7132359 1.8872550 2.6372541 1.0857818
## [43] 1.2132359 0.6617636 0.6151965 1.0343117 0.9362738 1.0073527 0.4607834
## [50] 0.4215687
From this one example, we can see that the JPM week price range difference was not always greater than any other week of the year, but it was in the top 20% of weeks (10 points above red line implies 10 weeks had a price range >1x JPM week price range).
Are there any years for ABBV for which the JPM week price range was the maximal range for the year?
## 3 4 5 6 7 8 9 10 11 12 13 14
## 2013 FALSE FALSE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2014 TRUE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE FALSE TRUE FALSE
## 2015 TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2016 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2017 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 15 16 17 18 19 20 21 22 23 24 25
## 2013 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE FALSE
## 2014 FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE
## 2015 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2016 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2017 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 26 27 28 29 30 31 32 33 34 35 36 37
## 2013 FALSE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE FALSE TRUE
## 2014 FALSE TRUE FALSE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2015 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE
## 2016 TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## 2017 TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE FALSE FALSE
## 38 39 40 41 42 43 44 45 46 47 48
## 2013 FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE TRUE TRUE
## 2014 FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
## 2015 TRUE FALSE TRUE TRUE TRUE FALSE FALSE TRUE TRUE TRUE TRUE
## 2016 TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE
## 2017 TRUE FALSE TRUE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE
## 49 50 51 52 53
## 2013 FALSE FALSE FALSE TRUE TRUE
## 2014 FALSE FALSE FALSE TRUE TRUE
## 2015 TRUE TRUE TRUE TRUE TRUE
## 2016 TRUE TRUE TRUE TRUE TRUE
## 2017 TRUE FALSE TRUE TRUE NA
No, not quite. It was nearly the case in 2016.
Now, let’s extend this type of analysis to more tickers and also include a better summary visualization. I’ll create a table with years as rows, and then each ticker will be a column with the # weeks price range smaller than JPM week price range. We’ll be able to overlay all of these different tickers on the graph and we’ll look for trends.
The data is in “Wide” form right now, which is not very tidy! I’ll reshape into “Long” form for ease of compatibility with ggplot()
There are some zero values in this table that should really be NAs, so I’ll correct that now.
Plot the summary figure.
Yikes, that’s ugly and hard to interpret. Let’s try again.
Let’s change week per year to a percentage of weeks to ease interpretation.
Plot percentages.
## quartz_off_screen
## 2
Let’s define performance as % increase from start to end of JPMHC week.
Change abline from no change to average weekly change that year.
Grey line is the average price change from Price.open at the beginning of the week to price.close at the end of the week for all weeks in the year after JPM.
Will need to limit analysis to 2010-2018 because those are the dates for which I have full years of both the biotech and s&P500 index information.
Calculate an empirical pvalue for each year: JPMHC week ratio >= other week ratios + 1 / # other weeks + 1 (JPM week)
## Warning: `as_tibble.matrix()` requires a matrix with column names or a `.name_repair` argument. Using compatibility `.name_repair`.
## This warning is displayed once per session.
## [1] TRUE
## [1] 429
## [1] 457
## [1] "2018-11-15"
## [1] "2018-11-16"
## [1] 1
## [1] 0.1303458 1.7506577 -0.8433294 3.1958573 1.6270782 -0.1557898
## [7] -1.7675502 2.6744578
## [1] 11
## [1] 24
## [1] 6
## [1] 9
## [1] 4
## [1] 28
## [1] 17
## [1] 43
## [1] 9
## [1] 0.21153846 0.47058824 0.11538462 0.17307692 0.07692308 0.53846154
## [7] 0.32692308 0.84313725 0.17307692
## [1] 20
## [1] 8
## [1] 39
## [1] 4
## [1] 8
## [1] 22
## [1] 3
## [1] 0.39215686 0.16000000 0.78000000 0.09302326 0.21052632 0.42307692
## [7] 0.05769231
plot(c(2010, 2012:2016, 2018), BTK_pvals, ylim = c(0, 1), ylab = "P-value", xlab = "Date", pch = 19, main = "Biotech indices and S&P500 index stocks perform comparably\nduring JPMHC week vs later weeks in same year")
abline(h = 0.05, col = "red")
points(2010:2018, pvals, ylim = c(0, 1), ylab = "P-value", xlab = "Date", pch = 19, main = "NBI index and S&P500 index stocks perform comparably\nduring JPMHC week vs later weeks in the year", col = "blue")
legend("topright", legend = c("NYSE", "NASDAQ"), col = c("black", "blue"), pch = 19)
## quartz_off_screen
## 2
## quartz_off_screen
## 2
Done! Let’s interpret this data in light of my original hypotheses:
What is might complicate the interpretation of the data:
Helpful introductory tutorial to stock data in R by Curtis Miller: https://ntguardian.wordpress.com/2017/03/27/introduction-stock-market-data-r-1/
Example of how GetBatchSymbols works and improves upon quantmod library by Marcelo Perlin: https://cran.r-project.org/web/packages/BatchGetSymbols/vignettes/BatchGetSymbols-vignette.html